package widget.common.tools
{
	import flash.display.DisplayObjectContainer;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.URLRequest;
	import flash.net.URLStream;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Timer;

	import mx.controls.ProgressBar;
	import mx.controls.ProgressBarMode;
	import mx.managers.PopUpManager;

	/**
	 * dispatch when a dll is installed into current domain
	 */
	[Event(name="install" , type="widget.common.tools.DLLsLoader")]

	/**
	 * dispatch when all dlls loaded and installed
	 */
	[Event(name="allCompleted" , type="widget.common.tools.DLLsLoader")]

	[Event(name="open" , type="flash.events.Event")]
	[Event(name="complete" , type="flash.events.Event")]
	[Event(name="progress" , type="flash.events.ProgressEvent")]
	[Event(name="ioError" , type="flash.events.IOErrorEvent")]
	[Event(name="httpStatus" , type="flash.events.HTTPStatusEvent")]
	[Event(name="securityError" , type="flash.events.SecurityErrorEvent")]

	/**
	 * DLL loader will load the DLLs into current domain.
	 * you should call addDll() to add dlls in to it's work queue,
	 * and then, call notify(), the queue will be processed automatically.
	 * after all the dlls loaded and installed, it will dispatch a "allCompleted" event
	 *
	 * @author Hukuang
	 */
	public class DLLsLoader extends EventDispatcher
	{

		public static const INSTALL : String = "install";
		public static const ALL_COMPLETED : String = "all completed";
		public static const MAX_RECONNECT : uint = 5;

		private var stream : URLStream;
		private var loader : Loader;

		private var dlls : Array = null;
		private var currentDLL : DLL;
		private var dllCount : int = 0;
		private var dllLoadedCount : int = 0;

		private var startTime : Number;
		private var _currentSpeed : Number;

		private const MAX_FAULT_COUNT : int = 3;
		private var faultCount : int = 0;
		private var dataObj : Object = null;

		/**
		 * 连接次数
		 */
		private var connectNum : int = 0;
		/**
		 * 加载进度时间监听
		 */
		private var progressTimer : Timer = null;
		/**
		 * 时间计数器
		 */
		private var timerCounter : int = 0;
		/**
		 * 连接失败次数计数器
		 */
		private var failedCounter : int = 0;
		/**
		 * 重新次数连接计数器
		 */
		private var reconnectNum : int = 0;

		private var rootContainer : DisplayObjectContainer = null;
		private var progressBar : ProgressBar = null;

		private var _content : * = null;
		private var isRunning : Boolean = false;

		public function DLLsLoader()
		{
			this.stream = new URLStream();
			this.stream.addEventListener(Event.OPEN , openHandler);
			this.stream.addEventListener(ProgressEvent.PROGRESS , progressHandler);
			this.stream.addEventListener(Event.COMPLETE , streamCompleteHandler);
			this.stream.addEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);
			this.stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR , securityErrorHandler);
			this.stream.addEventListener(HTTPStatusEvent.HTTP_STATUS , httpStatusHandler);

			progressTimer = new Timer(1000);
			progressTimer.start();
			progressTimer.addEventListener(TimerEvent.TIMER , timerHandler);
		}

		/**
		 * @param path the dll swf path
		 * @param path the dll name to display
		 * @param path the method to be execute when this dll is loaded, a param root will be pass to this method
		 */
		public function addDLL(path : String , displayName : String , executeMethodName : String = null) : void
		{
			var dll : DLL = new DLL();
			dll.path = path;
			dll.desplayName = displayName;
			dll.executeMethodName = executeMethodName;
			if (!dlls)
			{
				dlls = new Array();
			}
			var i : int = 0;
			var isHave : Boolean = false;
			var mode : DLL = null;
			while (i < dlls.length)
			{
				mode = dlls[i];
				if (mode.desplayName == dll.desplayName)
				{
					isHave = true;
					break;
				}
				i++;
			}
			if (!isHave)
			{
				dlls.push(dll);
				dllCount++;
			}
		}

		public function notify(root : DisplayObjectContainer) : void
		{
			rootContainer = root;
			if (!isRunning)
			{
				this.process();
			}
		}


		public function get currentDisplayName() : String
		{
			return this.currentDLL.desplayName;
		}

		public function get loadedCount() : uint
		{
			return this.dllLoadedCount;
		}

		public function get totalCount() : uint
		{
			return this.dllCount;
		}

		public function get currentSpeed() : Number
		{
			return _currentSpeed;
		}

		public function get currentContent() : *
		{
			return _content;
		}

		public function setDataObject(obj : Object) : void
		{
			dataObj = obj;
		}

		protected function process() : void
		{
			this.currentDLL = this.dlls.shift();

			if (this.currentDLL != null)
			{
				this.loadStream();
			}
			else
			{
				if (progressTimer && progressTimer.running)
				{
					clearTime();
				}
				if (rootContainer)
				{
					PopUpManager.removePopUp(progressBar);
					progressBar = null;
				}
				isRunning = false;
				this.dispatchEvent(new NetStatusEvent(DLLsLoader.ALL_COMPLETED , false , false , dataObj));
			}
		}

		protected function loadStream() : void
		{
			if (rootContainer)
			{
				var loaded : int = dllCount;
				var total : int = dllLoadedCount;
				showProgressBar();
				progressBar.setProgress(loaded , total);
				progressBar.label = "加载库文件  " + Math.round((loaded / total) * 100) + "%";
			}
			if (currentDLL)
			{
				var request : URLRequest = new URLRequest(this.currentDLL.path);
				stream.load(request);
			}
		}

		protected function loadBytes(bytes : ByteArray) : void
		{
			var context : LoaderContext = new LoaderContext(false , ApplicationDomain.currentDomain);
			if (loader == null)
			{
				loader = new Loader();
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE , loaderCompleteHandler);
			}
			loader.loadBytes(bytes , context);
		}

		private function showProgressBar() : void
		{
			if (!progressBar)
			{
				progressBar = new ProgressBar();
				progressBar.mode = ProgressBarMode.MANUAL;
				PopUpManager.addPopUp(progressBar , rootContainer , false);
				PopUpManager.centerPopUp(progressBar);
			}
		}

		/**
		 * 控制重新连接服务器
		 * @param event
		 *
		 */
		private function timerHandler(event : TimerEvent) : void
		{

			if (timerCounter == connectNum)
			{
				//掉线需要重新连接
				failedCounter++;

				if (failedCounter == 3)
				{
					failedCounter = 0;
					reconnectNum++;
					loadStream();
				}

				if (reconnectNum == DLLsLoader.MAX_RECONNECT)
				{
					//网络断掉
					reconnectNum = 0;
					clearTime();
				}
			}
			else
			{
				timerCounter = connectNum;
			}
		}

		private function openHandler(e : Event) : void
		{
			var now : Date = new Date();
			this.startTime = now.getTime();
			isRunning = true;
			this.dispatchEvent(e);
		}

		private function ioErrorHandler(e : IOErrorEvent) : void
		{
			this.faultCount++;

			if (this.faultCount > this.MAX_FAULT_COUNT)
			{
				this.dispatchEvent(e);
			}
			else
			{
				var timer : Timer = new Timer(500 , 1);
				timer.addEventListener(TimerEvent.TIMER_COMPLETE , function(e : TimerEvent) : void
					{
						loadStream();
					});
				timer.start();
			}
		}

		private function securityErrorHandler(e : SecurityErrorEvent) : void
		{
			this.faultCount++;

			if (this.faultCount > this.MAX_FAULT_COUNT)
			{
				this.dispatchEvent(e);
			}
			else
			{
				var timer : Timer = new Timer(500 , 1);
				timer.addEventListener(TimerEvent.TIMER_COMPLETE , function(e : TimerEvent) : void
					{
						loadStream();
					});
				timer.start();
			}
		}

		private function httpStatusHandler(e : HTTPStatusEvent) : void
		{
			this.dispatchEvent(e);
		}

		private function progressHandler(e : ProgressEvent) : void
		{
			var now : Date = new Date();
			var elapsedTime : Number = now.getTime() - this.startTime;
			this._currentSpeed = e.bytesLoaded / elapsedTime;
			connectNum++;
			this.dispatchEvent(e);
		}

		private function streamCompleteHandler(e : Event) : void
		{
			//trace ("dll streamed");

			var bytes : ByteArray = new ByteArray();
			var length : int = this.stream.bytesAvailable;
			this.stream.readBytes(bytes , 0 , length);
			this.stream.close();

			//trace ("dll byted");
			this.dispatchEvent(new Event(DLLsLoader.INSTALL));

			//install dll
			loadBytes(bytes);
		}

		private function loaderCompleteHandler(e : Event) : void
		{
			//trace ("dll installed");
			//var target : LoaderInfo = e.currentTarget as LoaderInfo;
			this.dllLoadedCount++;

			_content = loader.content;
			/* if (currentDLL.executeMethodName != null)
			   {
			   var content : * = this.loader.content;
			   content[currentDLL.executeMethodName](root);
			 } */
			this.dispatchEvent(e);
			//load next
			this.process();
		}

		private function clearTime() : void
		{
			if (progressTimer)
			{
				progressTimer.stop();
				progressTimer.removeEventListener(TimerEvent.TIMER , timerHandler);
				progressTimer = null;
			}
		}

		public function dispose() : void
		{
			clearTime();
			if(stream.connected){
				stream.close();
			}
			dlls.splice(0);
			dlls = null;
			currentDLL = null;
			rootContainer = null;
			stream = null;
		}
	}
}

class DLL
{
	public var path : String;
	public var desplayName : String;
	public var executeMethodName : String;
}

